/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Microsoft Corporation
 * 
 * -=- Robust Distributed System Nucleus (rDSN) -=- 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*
 * Description:
 *     factory registry for object creation
 *
 * Revision history:
 *     Mar., 2015, @imzhenyu (Zhenyu Guo), first version
 *     xxxx-xx-xx, author, fix bug about xxx
 */

# pragma once

# include <dsn/utility/singleton_store.h>

namespace dsn { 
    
    enum provider_type
    {
        PROVIDER_TYPE_MAIN = 0,
        PROVIDER_TYPE_ASPECT = 1
    };

    namespace utils {

    template<typename TResult>
    class factory_store
    {
    public:
        template<typename TFactory>
        static bool register_factory(const char* name, TFactory factory, ::dsn::provider_type type)
        {
            factory_entry entry;
            entry.dummy = nullptr;
            entry.factory = (void*)factory;
            entry.type = type;
            return singleton_store<std::string, factory_entry>::instance().put(std::string(name), entry);
        }

        template<typename TFactory>
        struct typed_factory_entry
        {
            std::string name;
            ::dsn::provider_type type;
            TFactory factory;
        };

        template<typename TFactory>
        static std::vector<typed_factory_entry<TFactory> > get_all_factories()
        {
            std::vector<std::string> keys;
            singleton_store<std::string, factory_entry>::instance().get_all_keys(keys);

            std::vector<typed_factory_entry<TFactory> > fs;
            for (auto& k : keys)
            {                
                factory_entry fe;
                singleton_store<std::string, factory_entry>::instance().get(k, fe);

                typed_factory_entry<TFactory> f;
                f.name = k;
                f.type = fe.type;
                f.factory = *(TFactory*)&fe.factory;;

                fs.push_back(f);
            }

            return fs;
        }

        template<typename TFactory>
        static TFactory get_factory(const char* name, ::dsn::provider_type type)
        {
            factory_entry entry;
            if (singleton_store<std::string, factory_entry>::instance().get(std::string(name), entry))
            {
                if (entry.type != type)
                {
                    report_error(name, type);
                    return nullptr;
                }
                else
                {
                    TFactory f;
                    f = *(TFactory*)&entry.factory;
                    return f;
                }
            }
            else
            {
                report_error(name, type);
                return nullptr;
            }
        }

        template<typename T1, typename T2, typename T3, typename T4, typename T5>
        static TResult* create(const char* name, ::dsn::provider_type type, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
        {
            typedef TResult* (*TFactory)(T1,T2,T3,T4,T5);
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f(t1, t2, t3, t4, t5) : nullptr;
        }

        template<typename T1, typename T2, typename T3, typename T4>
        static TResult* create(const char* name, ::dsn::provider_type type, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            typedef TResult* (*TFactory)(T1,T2,T3,T4);
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f(t1, t2, t3, t4) : nullptr;
        }

        template<typename T1, typename T2, typename T3>
        static TResult* create(const char* name, ::dsn::provider_type type, T1 t1, T2 t2, T3 t3)
        {
            typedef TResult* (*TFactory)(T1,T2,T3);
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f(t1, t2, t3) : nullptr;
        }

        template<typename T1, typename T2>
        static TResult* create(const char* name, ::dsn::provider_type type, T1 t1, T2 t2)
        {
            typedef TResult* (*TFactory)(T1,T2);
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f(t1, t2) : nullptr;
        }

        template<typename T1>
        static TResult* create(const char* name, ::dsn::provider_type type, T1 t1)
        {
            typedef TResult* (*TFactory)(T1);
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f(t1) : nullptr;
        }

        static TResult* create(const char* name, ::dsn::provider_type type)
        {
            typedef TResult* (*TFactory)();
            TFactory f = get_factory<TFactory>(name, type);
            return f ? f() : nullptr;
        }

    private:
        static void report_error(const char* name, ::dsn::provider_type type)
        {
            printf("cannot find factory '%s' with factory type %s\n", name, type == PROVIDER_TYPE_MAIN ? "provider" : "aspect");

            std::vector<std::string> keys;
            singleton_store<std::string, factory_entry>::instance().get_all_keys(keys);
            printf ("\tthe following %u factories are registered for '%s':\n", 
                static_cast<int>(keys.size()),
                typeid(TResult).name()
                );
            for (auto it = keys.begin(); it != keys.end(); ++it)
            {
                factory_entry entry;
                singleton_store<std::string, factory_entry>::instance().get(*it, entry);        
                printf("\t\t%s (type: %s)\n", it->c_str(), entry.type == PROVIDER_TYPE_MAIN ? "provider" : "aspect");
            }
            printf ("\tPlease specify the correct factory name in your tool_app or in configuration file\n");

            std::abort();
        }

    private:
        struct factory_entry
        {
            TResult* dummy;
            void*    factory;
            ::dsn::provider_type  type;

            factory_entry()
            {
                dummy = nullptr;
                factory = nullptr;
                type = PROVIDER_TYPE_MAIN;
            }
        };
    };

}} // end namespace dsn::utils
